#include "game.hpp"

#include "configuration.hpp"
#include "console.hpp"
#include "graphics.hpp"
#include "guichan.hpp"
#include "logger.hpp"
#include "imageloader.hpp"
#include "input.hpp"
#include "room.hpp"
#include "splash.hpp"
#include "resourcehandler.hpp"
#include "actor/player.hpp"
#include "tile/tilemap.hpp"
#include "tile/tilemaploader.hpp"
#include "util/stringutil.hpp"

#include "SDL.h"
#include <cmath>
#include <sstream>

namespace nmc
{
	Game* Game::mInstance = 0;

    Game::Game()
		: mRunning(true),
		  mState(PLAYING),
		  mUpKeyHeldDown(false),
		  mRightKeyHeldDown(false),
		  mDownKeyHeldDown(false),
		  mLeftKeyHeldDown(false),
		  mActionKeyHeldDown(false),
		  mJumpKeyHeldDown(false)
    {
       
    }

	Game* Game::instance()
	{
		if (mInstance == 0)
		{
			mInstance = new Game();
		}

		return mInstance;
	}

	void Game::start()
	{
		SDL_Init(SDL_INIT_VIDEO);
		SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
		SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);
		SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_OPENGL | SDL_GL_DOUBLEBUFFER | SDL_HWACCEL);
		SDL_EnableUNICODE(1);
		SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
		SDL_WM_SetCaption("Nodajo's Mechanical Castle", 0);

		mGraphics = new Graphics();
		mGraphics->setTargetPlane(640, 480);
		mGraphics->createRenderToTexture(1024, 512);

		mImageLoader = new ImageLoader();
		gcn::Image::setImageLoader(mImageLoader);
		
		mInput = new Input();

		mGui = new gcn::Gui();
		mGui->setGraphics(mGraphics);
		mGui->setInput(mInput);
		mGui->addGlobalKeyListener(this);

		mTop = new gcn::Container();
		mTop->setSize(640, 480);
		mGui->setTop(mTop);

		mConfiguration = new Configuration();
		mConsole = new Console();
		mLogger = new Logger();
		mResourceHandler = new ResourceHandler();
		mSplash = new Splash();
		mTileMapLoader = new TileMapLoader();

		mTileMap = mTileMapLoader->getTileMap("data/testtilemap.txt");
		mRoom = new Room();
		mRoom->setTileMap(mTileMap);

		mDarkbitsLogo = getImage("data/darkbitslogo_by_haiko.bmp");
		mFont = getFont("data/gamefont2.bmp", 32, 127);
		mPlayer = new Player();
		mPlayer->setPosition(Point(16, 16));

	}

	void Game::shutdown()
	{
		delete mConfiguration;
		delete mConsole;
		delete mGraphics;
		delete mGui;
		delete mImageLoader;
		delete mInput;
		delete mLogger;
		delete mPlayer;
		delete mResourceHandler;
		delete mRoom;
		delete mSplash;
		delete mTileMapLoader;
		delete mTileMap;
		delete mTop;

		SDL_Quit();
	}

    void Game::run()
    {
		long lastLogicFrame = getTicks();
		long lastGraphicsFrame = getTicks();

		int graphicFrames = 0;
		int logicFrames = 0;
		long lastFrameRateReport = getTicks();

		while (mRunning)
		{
			if (getTicks() - lastLogicFrame >= (1000 / LOGIC_FRAME_RATE))
			{
				lastLogicFrame = getTicks();
				logic();
				logicFrames++;
			}
           
			if (getTicks() - lastGraphicsFrame >= (1000 / GRAPHICS_FRAME_RATE))
            {
				lastGraphicsFrame = getTicks();
				draw();
				graphicFrames++;
			}

			if ((getTicks() - lastLogicFrame < (1000 / LOGIC_FRAME_RATE))
				&& (getTicks() - lastGraphicsFrame < (1000 / GRAPHICS_FRAME_RATE)))
			{
				SDL_Delay(1);
			}

			if (getTicks() - lastFrameRateReport >= 1000)
			{
				std::string str;
				std::ostringstream os(str);
				os << "LFR: " << logicFrames << " GFR: " << graphicFrames;
				log(os.str());
				lastFrameRateReport = getTicks();
				logicFrames = 0;
				graphicFrames = 0;
			}
		}
    }

	void Game::logic()
	{
        SDL_Event sdlEvent;
        while(SDL_PollEvent(&sdlEvent))
        {
			if (sdlEvent.type == SDL_QUIT)
            {
                mRunning = false;
			}

			mInput->pushInput(sdlEvent);
        }

		mGui->logic();

		mPlayer->logic(mRoom);
	}

	void Game::draw()
	{
		glClear(GL_COLOR_BUFFER_BIT);
		mGraphics->_beginDraw();		
		
		if (mState == PLAYING)
		{
			mGraphics->beginRenderToTexture();
		}
		
		mTileMap->drawTypes(mGraphics);
		mTileMap->drawSolidities(mGraphics);
		mPlayer->drawDimension(mGraphics);

		if (mState == PLAYING)
		{
			mGraphics->endRenderToTexture();
			glClear(GL_COLOR_BUFFER_BIT);
			mGraphics->drawRenderToTexture(0, 0, 640, 480);
		}

		if (mState != PLAYING)
		{
			gcn::Font* font = getFont("data/gamefont2.bmp", 32, 127);
			mGraphics->setFont(font);
			mGraphics->setColor(Color(0xffffff));
			mGraphics->drawText("NMC Debug", 0, 480 - font->getHeight());
		}

		mGraphics->_endDraw();
		SDL_GL_SwapBuffers();
	}

	void Game::log(const std::string& message)
	{
		mLogger->log(message);
	}

	Console* Game::getConsole()
	{
		return mConsole;
	}

	gcn::Gui* Game::getGui()
	{
		return mGui;
	}

	long Game::getTicks()
	{
		return SDL_GetTicks();
	}

	Image* Game::getImage(const std::string& filename)
	{
		return mResourceHandler->getImage(filename);
	}

	gcn::Font* Game::getFont(const std::string& filename,
		                     unsigned char glyphsFrom,
						     unsigned char glyphsTo)
	{
		return mResourceHandler->getFont(filename, glyphsFrom, glyphsTo);
	}

	TileMap* Game::getTileMap(const std::string& filename)
	{
		return mTileMapLoader->getTileMap(filename);
	}

	Configuration* Game::getConfiguration()
	{
		return mConfiguration;
	}

	void Game::keyPressed(gcn::KeyEvent& keyEvent)
	{
		const gcn::Key key = keyEvent.getKey();
	
		if (key.getValue() == int('q'))
		{
			mRunning = false;
			return;
		}
		else if (key.getValue() == gcn::Key::F1)
		{
			mState = PLAYING;
		}
		else if (key.getValue() == gcn::Key::F2)
		{
			mState = DEBUGGING;
		}
		else if (key.getValue() == gcn::Key::F3)
		{
			mState = EDITING;
		}
		else if (mConfiguration->getUpKey() == key)
		{
			mUpKeyHeldDown = true;
		}
		else if (mConfiguration->getRightKey() == key)
		{
			mRightKeyHeldDown = true;
		}
		else if (mConfiguration->getDownKey() == key)
		{
			mDownKeyHeldDown = true;
		}
		else if (mConfiguration->getLeftKey() == key)
		{
			mLeftKeyHeldDown = true;
		}
		else if (mConfiguration->getActionKey() == key)
		{
			mActionKeyHeldDown = true;
		}
		else if (mConfiguration->getJumpKey() == key)
		{
			mJumpKeyHeldDown = true;
		}
	}

	void Game::keyReleased(gcn::KeyEvent& keyEvent)
	{
		const gcn::Key key = keyEvent.getKey();

		if (mConfiguration->getUpKey() == key)
		{
			mUpKeyHeldDown = false;
		}
		else if (mConfiguration->getRightKey() == key)
		{
			mRightKeyHeldDown = false;
		}
		else if (mConfiguration->getDownKey() == key)
		{
			mDownKeyHeldDown = false;
		}
		else if (mConfiguration->getLeftKey() == key)
		{
			mLeftKeyHeldDown = false;
		}
		else if (mConfiguration->getActionKey() == key)
		{
			mActionKeyHeldDown = false;
		}
		else if (mConfiguration->getJumpKey() == key)
		{
			mJumpKeyHeldDown = false;
		}
	}

	bool Game::isUpKeyHeldDown()
	{
		return mUpKeyHeldDown;
	}

	bool Game::isRightKeyHeldDown()
	{
		return mRightKeyHeldDown;
	}

	bool Game::isDownKeyHeldDown()
	{
		return mDownKeyHeldDown;
	}

	bool Game::isLeftKeyHeldDown()
	{
		return mLeftKeyHeldDown;
	}

	bool Game::isActionKeyHeldDown()
	{
		return mActionKeyHeldDown;
	}

	bool Game::isJumpKeyHeldDown()
	{
		return mJumpKeyHeldDown;
	}
}